/* eslint-disable @typescript-eslint/ban-ts-comment */
/* eslint-disable no-useless-escape */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unused-vars */
import {
  forEach,
  get,
  pickBy,
  intersection,
  isEqual,
  isPlainObject,
} from 'lodash-es';
export {
  debounce,
  throttle,
  cloneDeep,
  chunk,
  random,
  merge,
  isEqual,
  pick,
  omit,
  pickBy,
  omitBy,
} from 'lodash-es';
import { useEffect, useRef } from 'react';

type timeType = string | number | Date;

const timeFormatRules: [string, keyof Date][] = [
  ['YYYY', 'getFullYear'],
  ['MM', 'getMonth'],
  ['DD', 'getDate'],
  ['HH', 'getHours'],
  ['mm', 'getMinutes'],
  ['ss', 'getSeconds'],
];

/** 字符串补0 */
export function fillZero(data: unknown, separator?: string): string {
  const safeData = String(data);
  const safeSeparator = String(separator);
  const handler = (arg: string) => arg.padStart(2, '0');
  if (separator)
    return safeData.split(safeSeparator).map(handler).join(safeSeparator);
  return handler(safeData);
}

/** 数组去重 */
export function toSet<T>(data: T[], path?: string): T[] {
  const safePath = String(path);
  if (!Array.isArray(data)) return [];
  if (path) {
    const map = new Map();
    return data.filter((v) => {
      const val = get(v, safePath);
      return !map.has(val) && map.set(val, 1);
    });
  }
  return Array.from(new Set(data));
}

/** 数组排序  */
export function doSort<T>(
  data: T[],
  order: 'asc' | 'desc' = 'asc',
  mode: 'number' | 'english' | 'chinese' = 'number',
  path?: string,
): T[] {
  if (!Array.isArray(data)) return [];
  const safePath = String(path);
  const handler = (arg1: T, arg2: T) => {
    if (mode === 'number') return Number(arg1) - Number(arg2);
    return String(arg1).localeCompare(
      String(arg2),
      mode === 'chinese' ? 'zh-CN' : void 0,
    );
  };
  const getParam = (arg: T): T => (path ? get(arg, safePath) : arg);
  return data.sort((...args) => {
    const [arg1, arg2] = (order === 'asc' ? args : args.reverse()) as [T, T];
    return handler(getParam(arg1), getParam(arg2));
  });
}

/** 获得最值 */
export function getExtreme<T>(
  data: T[],
  mode: 'max' | 'min' = 'max',
  path?: string,
): T | undefined {
  if (!Array.isArray(data)) return void 0;
  const safePath = String(path);
  if (path) {
    const temp = Math[mode](...data.map((v) => Number(get(v, safePath))));
    return data.find((v) => get(v, safePath) === temp) as T;
  }
  return Math[mode](...data.map((v) => Number(v))) as T;
}

/**  时间格式化 */
export function timeFormat(
  date: timeType,
  format = 'YYYY-MM-DD HH:mm:ss',
): string {
  const dateObj = new Date(date);
  const tF = timeFormatRules.reduce((pre, _this, index): string => {
    const [symbol, func] = _this;
    if (!format.includes(symbol)) return pre;
    //@ts-ignore
    const val = Number(dateObj[func]());
    const rs = pre.replace(symbol, fillZero(index === 1 ? val + 1 : val));
    return rs;
  }, format);
  return tF;
}

/** 获取时差 */
export function timeGap(
  t1: timeType,
  t2: timeType,
  format?: string,
): string | string[] {
  const { trunc, abs, round } = Math;
  const day_ms = 24 * 60 * 60 * 1000;
  const getMs = (arg: timeType) => new Date(arg).getTime();
  const diff_ms = abs(getMs(t1) - getMs(t2));
  const list = [diff_ms / day_ms];
  Array.of(24, 60, 60).reduce((pre, _this) => {
    const rs = (pre % 1) * _this;
    list.push(rs);
    return rs;
  }, list[0]);
  const formatList = list.map((v, i) =>
    fillZero(i === 3 ? round(v) : trunc(v)),
  );
  let rs;
  if (format) {
    rs = timeFormatRules.slice(2).reduce((pre, _this, index) => {
      // @ts-ignore
      return pre?.replace(_this[0], formatList[index]);
    }, format);
  }
  return rs ?? formatList;
}

/** 剩余时间 */
export function remainTime(
  remainTime: number,
  format?: string,
): string | string[] {
  const { trunc, round } = Math;
  const day_ms = 24 * 60 * 60 * 1000;
  const diff_ms = Number(remainTime);
  const list = [diff_ms / day_ms];
  Array.of(24, 60, 60).reduce((pre, _this) => {
    const rs = (pre % 1) * _this;
    list.push(rs);
    return rs;
  }, list[0]);
  const formatList = list.map((v, i) =>
    fillZero(i === 3 ? round(v) : trunc(v)),
  );
  let rs;
  if (format) {
    rs = timeFormatRules.slice(2).reduce((pre, _this, index) => {
      // @ts-ignore
      return pre?.replace(_this[0], formatList[index]);
    }, format);
  }
  return rs ?? formatList;
}

/** 排序效仿 */
export function sortImitate<T>(target: T[], source: T[], path: string): T[] {
  if (!Array.isArray(target) || !Array.isArray(source)) return [];
  const safePath = String(path);
  const arr: T[] = [];
  target.forEach((v, i) => {
    source.forEach((v1) => {
      get(v, safePath) === get(v1, safePath) && (arr[i] = v1);
    });
  });
  return arr;
}

/**  随机数范围原理： math.random * (end - start) + start */

/** 文字超出省略 */
export function textEllipsis(
  text: string,
  length: number,
  strict?: boolean,
): string {
  if (isNaN(Number(length))) return '';
  const safeLength = Math.trunc(Number(length));
  const safeText = String(text);
  let totalLength = 0;
  let initStr = '';
  let count = 0;
  for (const _iterator of safeText) {
    totalLength++;
  }
  if (totalLength <= safeLength) return safeText;
  for (const each of safeText) {
    initStr += each;
    count++;
    if (count >= (strict ? safeLength - 3 : safeLength)) return initStr + '...';
  }
  return safeText;
}

/** 获得数据类型 */
export function getType(e: unknown): string {
  const rs = Object.prototype.toString.call(e).match(/\s(.+)\]/i);
  return rs ? rs[1].toLocaleLowerCase() : 'Unknown Type';
}

/** 嵌套对象结构拉平 */
export function flatObj<T extends Record<string, any>>(data: T[]): T[] {
  if (!Array.isArray(data)) return [];
  const target = [] as T[];
  const handler = (curData: T): void => {
    const { children, ...rest } = curData;
    target.push(rest as T);
    if (Boolean(children) && Array.isArray(children))
      children.forEach((v): void => handler(v));
  };
  data.forEach((v) => handler(v));
  return target;
}

/** 平级对象结构嵌套 */
export function nestedObj<T extends Record<string, any>>(
  data: T[],
  id: string,
  pid: string,
): T[] {
  if (!Array.isArray(data)) return [];
  return data.filter((v) => {
    let mark = true;
    data.forEach((v2) => {
      if (v[String(pid)] === v2[String(id)]) {
        //@ts-ignore
        !v2.children && (v2.children = []);
        v2.children?.push?.(v);
        mark = false;
      }
    });
    return mark;
  });
}

/** 获得滚动条宽度 */
export function getScrollBarWidth(): number {
  const outer = document.createElement('div');
  outer.className = 'yjr-scrollbar__wrap';
  outer.style.visibility = 'hidden';
  outer.style.width = '100px';
  outer.style.position = 'absolute';
  outer.style.top = '-9999px';
  document.body.appendChild(outer);

  const NoScroll = outer.offsetWidth;
  outer.style.overflow = 'scroll';

  const inner = document.createElement('div');
  inner.style.width = '100%';
  outer.appendChild(inner);

  const WithScroll = inner.offsetWidth;
  document.body.removeChild(outer);
  return NoScroll - WithScroll;
}

/** 获取ios安全区域数值 */
export function getSafeArea(): Promise<{
  safeTop: number;
  safeBottom: number;
}> {
  /**
   * 你需要在文档中加入如下标签才可能获取到正确的信息
   * <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=0, minimum-scale=1, maximum-scale=1, viewport-fit=cover">
   */
  if (!isIOS()) {
    return Promise.resolve({
      safeTop: 0,
      safeBottom: 0,
    });
  }
  return new Promise((resolve) => {
    const disposable = document.createElement('div');
    disposable.style.visibility = 'hidden';
    disposable.style.paddingTop = 'env(safe-area-inset-top)';
    disposable.style.paddingBottom = 'env(safe-area-inset-bottom)';
    disposable.style.position = 'absolute';
    disposable.style.top = '-9999px';
    disposable.style.height = '100px';
    disposable.style.boxSizing = 'border-box';
    document.body.appendChild(disposable);
    const resizeObserver = new ResizeObserver((doms) => {
      const curDom = doms[0];
      const { top, bottom, height } = curDom.contentRect;
      if (height !== 100) {
        resolve({
          safeTop: top,
          safeBottom: 100 - bottom,
        });
        resizeObserver.unobserve(disposable);
        document.body.removeChild(disposable);
      }
    });
    resizeObserver.observe(disposable);
  });
}

/** 获得地址栏里的参数 */
export function getURLParameters(): Record<string, string> {
  if (window)
    return Object.fromEntries(
      window.location.search
        .slice(1)
        .split('&')
        .map((v) => v.split('=')),
    );
  return {};
}

/** 货币格式化 */
export function currencyFormat(num: number | string): string {
  return String(num).replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}

/** 将对象转成查询字符串 */
export function queryString(data: Record<string, any>): string {
  return Object.entries(pickBy(data, (v) => v !== undefined && v !== null))
    .map((v) => v.join('='))
    .join('&');
}

/** 监听元素是否可见 */
export function watchDomIsVisible(
  dom: any,
  fn: (e: boolean) => void,
  threshold: number = 0.5,
): {
  watch: () => void;
  unwatch: () => void;
} {
  const jug = dom && dom.nodeType === 1 && IntersectionObserver;
  if (!jug) return { watch: () => {}, unwatch: () => {} };
  const intersectionObserver = new IntersectionObserver(
    (entries) => fn(entries[0].isIntersecting),
    {
      threshold,
    },
  );
  return {
    watch: () => jug && intersectionObserver.observe(dom),
    unwatch: () => jug && intersectionObserver.unobserve(dom),
  };
}

/** 监听页面可视状态 */
export function pageIsVisible(fn: (e: 'hidden' | 'visible') => void): {
  watch: () => void;
  unwatch: () => void;
} {
  const handler = () => fn(document.visibilityState);
  return {
    watch: () => document.addEventListener('visibilitychange', handler),
    unwatch: () => document.removeEventListener('visibilitychange', handler),
  };
}

/** 是否是移动端设备 */
export function isMobile(): boolean {
  if (window)
    return /Mobi|Android|iPhone|iPod|iPad|Windows Phone|MQQBrowser/.test(
      navigator.userAgent,
    );
  return false;
}

/** 是否是IOS */
export function isIOS(): boolean {
  if (window)
    // @ts-ignore
    return /iPhone|iPad|iPod/.test(navigator.userAgent);
  return false;
}

/** H5应用布局适配ios键盘 */
export function fitKeyboardInIOS(app: any): void {
  const safeJug = app && app.nodeType === 1;
  if (!safeJug || !window) return;
  let handler;
  if (isMobile() && isIOS()) {
    handler = (e: any = {}) => {
      const realViewport = e.target;
      if (realViewport) {
        const height = realViewport.height ?? innerHeight;
        let bottom = 0;
        if (height < innerHeight) bottom = innerHeight - height;
        window.requestAnimationFrame(() => {
          app.style.position = 'fixed';
          app.style.left = '0px';
          app.style.bottom = bottom + 'px';
          app.style.height = (realViewport.height ?? innerHeight) + 'px';
          app.style.width = (realViewport.width ?? innerWidth) + 'px';
        });
      }
    };
  }
  if (handler) {
    window.visualViewport?.addEventListener('resize', handler);
    window.visualViewport?.addEventListener('scroll', handler);
  }
}

/** 继承useEffect，在effect基础上会返回你是哪些依赖触发的本次更新，并告诉你是否是第一次执行useEffect */
export function useEffectPlus(
  effect: (records: [boolean, number[]]) => (() => void) | void,
  deps: React.DependencyList,
) {
  const lastDeps = useRef<React.DependencyList[]>([]);
  const updateTimes = useRef(0);
  useEffect(() => {
    const handler = (curDeps: React.DependencyList): [boolean, number[]] => {
      if (!Array.isArray(curDeps)) return [true, []];
      const changedList: number[] = [];
      if (updateTimes.current > 0) {
        curDeps.forEach((item, index) => {
          if (item !== lastDeps.current[index]) {
            changedList.push(index);
          }
        });
      }
      if (updateTimes.current < 2) updateTimes.current += 1;
      lastDeps.current = curDeps;
      const isFirstUpdate = updateTimes.current === 1;
      return [
        isFirstUpdate,
        isFirstUpdate
          ? Array.from({ length: curDeps.length }, (_v, index) => index)
          : changedList,
      ];
    };
    return effect(handler(deps));
  }, [deps]);
}

/** 解析cookie成map映射 */
export function cookieParse(): Record<string, string> {
  if (window.document) {
    return Object.fromEntries(
      window.document.cookie.split('; ').map((v) => v.split('=')),
    );
  }
  return {};
}

function calculate(num: number) {
  return Number(num.toFixed(2));
}
/** AA分账算法 */
export function aaAccountingAlgorithm(input: string) {
  const formatInput = input
    .replace(/\s/g, '')
    .replaceAll('：', ':')
    .replaceAll('；', ';');
  // 将字符串切分为数组，分别存储在各个personDetails中
  const personDetails = formatInput
    .split(';')
    .filter((v) => /([\u4e00-\u9fa5\w]+):(\d+)/gi.test(v));
  // 创建一个对象来存储每个人的信息
  const people = {} as Record<string, number>;
  let totalAmount = 0;
  // 遍历数组，提取每个人的名字和所支付的钱
  for (const detail of personDetails) {
    const [name, amount] = detail.split(':');
    const value = Number(amount);
    people[name] = value;
    totalAmount += value;
  }
  // 计算每个人应该支付多少钱
  const average = calculate(totalAmount / Object.keys(people).length);
  // 计算每个人的应收应付款
  const balances = {} as Record<string, number>;
  for (const [name, amount] of Object.entries(people)) {
    balances[name] = calculate(amount - average);
  }
  // 找出需要给钱和收钱的人
  const creditors = [];
  const debtors = [];
  for (const [name, balance] of Object.entries(balances)) {
    if (balance > 0) {
      creditors.push({ name, amount: balance });
    } else if (balance < 0) {
      debtors.push({ name, amount: -balance });
    }
  }
  // 构建结算方案
  const transactions = [];
  for (const debtor of debtors) {
    while (debtor.amount > 0) {
      const creditor = creditors[0];
      const paidAmount = Math.min(debtor.amount, creditor.amount);
      transactions.push(
        `${debtor.name} 应该给 ${creditor.name} 转 ${paidAmount} 元`,
      );
      debtor.amount = calculate(debtor.amount - paidAmount);
      creditor.amount = calculate(creditor.amount - paidAmount);
      if (creditor.amount === 0) {
        creditors.shift();
      }
    }
  }
  // 输出结果
  return transactions.join(';');
}
/** 对比两组json之间的差异 */
export function jsonDiff(
  obj1: Record<string, any>,
  obj2: Record<string, any>,
  path = '',
) {
  try {
    const differences: {
      path: string;
      changeType: 'added' | 'removed' | 'modified';
      oldValue: any;
      newValue: any;
    }[] = [];

    forEach(obj2, (value, key) => {
      const currentPath = path ? `${path}.${key}` : key;
      if (!Reflect.has(obj1, key)) {
        differences.push({
          path: currentPath,
          changeType: 'added',
          oldValue: undefined,
          newValue: value,
        });
      }
    });

    forEach(obj1, (value, key) => {
      const currentPath = path ? `${path}.${key}` : key;
      if (!Reflect.has(obj2, key)) {
        differences.push({
          path: currentPath,
          changeType: 'removed',
          oldValue: value,
          newValue: undefined,
        });
      }
    });

    forEach(intersection(Object.keys(obj1), Object.keys(obj2)), (key) => {
      const currentPath = path ? `${path}.${key}` : key;
      const val1 = obj1[key];
      const val2 = obj2[key];
      if (!isEqual(val1, val2)) {
        if (
          (isPlainObject(val1) || Array.isArray(val1)) &&
          (isPlainObject(val2) || Array.isArray(val2))
        ) {
          differences.push(...jsonDiff(val1, val2, currentPath));
        } else {
          differences.push({
            path: currentPath,
            changeType: 'modified',
            oldValue: val1,
            newValue: val2,
          });
        }
      }
    });
    return differences;
  } catch (error) {
    console.error('yjrUtils.jsonDiff.runTime.error', error);
    return [];
  }
}

/** 复制文本 */
export function copyText(text: string) {
  return new Promise<void>((resolve, reject) => {
    const safeArg = String(text);
    const textArea = document.createElement('textarea');
    textArea.value = safeArg;
    textArea.style.top = '-9999px';
    textArea.style.left = '-9999px';
    textArea.style.position = 'fixed';
    document.body.appendChild(textArea);
    textArea.focus();
    textArea.select();
    try {
      const successful = document.execCommand('copy');
      if (successful) {
        resolve();
      } else {
        reject();
      }
    } catch (err) {
      reject();
    }
    document.body.removeChild(textArea);
  });
}

/** 该方法已废弃，请使用jsonDiff替代 */
// export function jsDiff(target: any, current: any): any[] {
//   type obj = Record<string, any>;
//   const isQuoteType = (arg: any) => {
//     const type = getType(arg);
//     return type === 'object' || type === 'array';
//   };
//   const diffType = (target: any, curVal: any, reverse?: boolean) => {
//     let str = '';
//     if (target !== undefined && curVal !== undefined) str = '修改';
//     else if (target !== undefined && curVal === undefined) {
//       str = reverse ? '新增' : '删除';
//     } else {
//       str = reverse ? '删除' : '新增';
//     }
//     return {
//       desc: str,
//       _old: reverse ? curVal : target,
//       _new: reverse ? target : curVal,
//     };
//   };
//   if (isQuoteType(current) && isQuoteType(target)) {
//     const init: any[] = [];
//     const initR: any[] = [];
//     const compare = (
//       pre: any,
//       cur: any,
//       init: obj,
//       reverse?: boolean,
//       lastKey?: string,
//     ) => {
//       for (const key in cur) {
//         let path = key;
//         const curVal = cur[key];
//         const preVal = pre?.[key];
//         if (
//           isQuoteType(curVal) &&
//           isQuoteType(preVal) &&
//           getType(curVal) === getType(preVal)
//         ) {
//           if (lastKey) path += `.${lastKey}`;
//           compare(preVal, curVal, init, reverse, path);
//         } else {
//           if (curVal !== preVal) {
//             if (!reverse || !(curVal !== undefined && preVal !== undefined)) {
//               const path = lastKey ? `${key}.${lastKey}` : key;
//               init.push({
//                 path: path.split('.').reverse().join('.'),
//                 ...diffType(preVal, curVal, reverse),
//               });
//             }
//           }
//         }
//       }
//     };
//     compare(target, current, init);
//     compare(current, target, initR, true);
//     return [...init, ...initR];
//   }

//   if (target === current) return [];
//   return [
//     {
//       path: '',
//       ...diffType(target, current),
//     },
//   ];
// }
